<?php
/**
 * 文件目录处理类
 */

class Dir
{
	/**
	 * 文件夹名
	 * @var string
	 */
	protected $dirname;

	protected $msg;

	/**
	 * 目录对象下的所有文件
	 * @var array
	 */
	protected $files;

	/**
	 * 构造函数
	 * 如果参数 $dirname 指定的目录不存在,那么将会自动生成该目录
	 * @param string $dirname 文件目录名, 若留空则指向调用 LIN_Dir 类的脚本所在的目录
	 */
	public function __construct($dirname=".")
	{
		$this->dirname = $dirname;
		if(!file_exists($dirname))
			$this->make();
		$this->dirname = str_replace("\\", "/", realpath($dirname));
		$this->files = array();
	}

	/**
	 * 用文件夹名直接输出
	 */
	public function __toString()
	{
		return $this->dirname;
	}

	/**
	 * 生成一个多级文件目录
	 * @param string $dirname 文件目录
	 * @return bool 创建成功,或者目标目录已经存在,则返回成功, 否则返回失败
	 */
	public function make($dirname='')
	{
		if(empty($dirname))
			$dirname = $this->dirname;
		if(empty($dirname) || $dirname == "." || $dirname == "..")
			return false;

		$dirname = str_replace("\\", "/", $dirname);
		$dirs = explode("/", $dirname);
		$dir = '';
		foreach($dirs as $d){
			$dir .= "$d";
			if($d != '.' && $d != '..' && !file_exists($dir)){
				if(!mkdir($dir)){
					return false;
				}
			}
			$dir .= '/';
		}

		return true;
	}

	/**
	 * 把本目录对象的目录复制到另外一个文件夹
	 * @param string/object $from 源目录,可以是目录名, 也可以是一个 LIN_Dir 对象
	 * @param string/object $to 目标目录,可以是目录名, 也可以是一个 LIN_Dir 对象
	 * @param bool $isRepplace 如果目标目录下有相同的名字的文件存在,是否覆盖已有文件, true则覆盖,否则跳过
	 * @return bool
	 */
	public function copy($from, $to, $isReplace=true)
	{
		if(is_object($from))
			$from = $from->dirname;
		if(is_object($to))
			$to = $to->dirname;
		if(!file_exists($from))
			$this->error("没有找到要被复制的目录");
		$files = scandir($from);

		$this->make($to);
		$f = '';
		for($i=2; isset($files[$i]); ++$i){
			$f = $from."/".$files[$i];
			if(is_file($f)){
				if(!$isReplace && file_exists($to, $files[$i]))
					continue;
				if(!copy($f, $to."/".$files[$i]))
					$this->error("创建 ". $from."/".$files[$i] ." 失败");;
			}else if(is_dir($f)){
				$this->copy($f, $to."/".$files[$i]);
			}
		}

		return true;
	}

	/**
	 * 把本目录对象的目录复制到另外一个文件夹
	 * @param string/object $to 目标目录,可以是目录名, 也可以是一个 LIN_Dir 对象
	 * @return bool
	 */
	public function copyTo($to)
	{
		return $this->copy($this, $to);
	}

	/**
	 * 把本目录对象的目录复制到另外一个文件夹
	 * @param string/object $from 目标目录,可以是目录名, 也可以是一个 LIN_Dir 对象
	 * @return bool
	 */
	public function copyFrom($from)
	{
		return $this->copy($from, $this);
	}

	/**
	 * 清空目录下的文件, 但保留本目录
	 * @param bool $omitDir 指出是否删除子目录
	 * @param string $backup 备份目录,若给出这个参数的值,那么在清空文件夹的时候,首先会备份到,若$backup缺省,则不备份.
	 */
	public function clean($omitDir=true, $backup=null)
	{
		if(rtrim($this->dirname, '/') == getcwd()
			|| rtrim($this->dirname) == str_replace("\\", "/", dirname(__FILE__))) {
			//如果被删的目录正是正在运行的程序文件所在目录,则拒绝删除操作请求:
			$this->error("不能删除 " . realpath($this->dirname) . " 目录, 运行的程序位于此目录");
			return ;
		}
		$backup = str_replace("\\", "/", realpath($backup));
		if($backup!=null){
			if($backup == str_replace("\\", "/", realpath($this->dirname))){
				$this->error(
					"不要将备份文件保存在将在被删除的目录里<br />\n".
					"备份文件的目录: $backup <br />\n".
					"待清空目录: {$this->dirname}<br />\n"
				);
				return ;
			}
			$this->copyTo($backup);
		}

		if(!isset($this->files[0]))
			$this->listFiles($this, false);
		foreach($this->files as $k=>$v){
			if(is_file($v)){
				unlink($v);
				$this->files[$k] = null;
				unset($this->files[$k]);
			}
		}
		if($omitDir==false){
			foreach($this->files as $k=>$v){
				rmdir($v);
				$this->files[$k]=null;
				unset($this->files);
			}
		}
	}

	/**
	 * 遍历目录, 对目录中每个文件或者子目录执行某个函数
	 * @param string $callback 回调函数
	 * 例如:$callback='unlink',
	 * 又如:$callback = 'file_put_contnets("$file", "hello")',
	 * 记得如果带括号的话, 要用单引号把整个回调函数括起来, 参数 $file 要用双引号或者 \' 括起来
	 */
	public function travel($callback)
	{
		$this->listFiles($this->dirname, false);
		$files = & $this->files;
		$return =null;
		$callback = ltrim($callback);
		if($callback{0}=='{'){
			# 如果传入的参数是一个匿名函数,即以 { 开始, 以 } 结束, 则:
			$callback = substr($callback, 1, strlen($callback)-2);
			$func = create_function('$file', $callback);
			$callback = '$func("$file")';
		}else if(strpos($callback, "(")===false){
			$callback = "return $callback".'("$file")';
		}
		foreach($files as $file){
			$return = eval($callback.";");
		}

		return $return;
	}

	/**
	 * 把本类对象目录空间转到另外一个目录
	 * @param string $newdir
	 */
	public function cd($newdir)
	{
		$newdir = rtrim($newdir, "/\\ ");
		$this->dirname = str_replace("\\", "/", realpath($newdir));
		$this->files = array();
		if(!file_exists($newdir)){
			$this->error($newdir . " 不存在");
			return false;
		}
		return true;
	}

	/**
	 * 在 $this->dirname 目录下创建一个文件
	 * 如果 $filename 包含文件目录,而且这些目录不存在,则会自动创建
	 */
	public function createFile($filename, $content='')
	{
		# 找到 $filename 中文件部分, 目录部分
		# 如果目录不存在,创建之
		if(strpos($filename, "/")!==false){
			$file = strrchr($filename, "/");
			$dir = str_replace($file, '', $filename);

			if(strpos($dir, ":")===false){//不是绝对路径
			$dir = rtrim($this->dirname,"/") . "/". $dir;
			}
			$this->make($dir);
			$filename = $dir."/".$file;
		}else{
			$filename = $this->dirname . "/" . $filename;
		}
		return file_put_contents($filename, $content);
	}

	/**
	 * 得到上一级目录名,即 LIN_Dir->dirname 被设置为上级目录
	 * @return true
	 */
	public function up()
	{
		return $this->dirname = dirname($this->dirname);
	}

	/**
	 * 得到文件目录的大小
	 */
	public function getSize()
	{
		// {{{
		//算法一:
		if(stripos(PHP_OS, "win")!==false){//For Windows
			$obj = new COM ( 'scripting.filesystemobject' );
			if ( is_object ( $obj ))
			{
				$ref = $obj->getfolder ( $this->dirname );
				$size = $ref->size;
				$obj = null;
				return $size;
			}
			else{
				return false;
			}
		}else{// For Linux-like
			$io = popen ( '/usr/bin/du -sk ' . $this->dirname, 'r' );
			$size = fgets ( $io, 4096);
			$size = substr ( $size, 0, strpos ( $size, ' ' ) );
			pclose ( $io );
			return $size;
		}
		// }}}

		/**
		//{{{
		//算法三:(执行太慢)
		$this->listFiles();
		$size=0;
		foreach($this->files as $file){
			$size += filesize($file);
		}
		return $size;
		// }}}
		**/
	}

	public function listFiles($theDir='', $omitDir=true)
	{
		if(empty($theDir))
			$theDir = $this->dirname;
		else if(is_object($theDir))
			$theDir = $theDir->dirname;
		if(!file_exists($theDir))
			return false;

		$dirs = scandir($theDir);

		unset($dirs[0], $dirs[1]);
		$f = '';
		foreach($dirs as $dir){
			$dir = $theDir . '/'.$dir;
			if(is_file($dir)){
				$this->files[] = $dir;
			}else if(is_dir($dir)){
				!$omitDir && $this->files[] = $dir ;
				$this->listFiles($dir, $omitDir);
			}
		}
		return $this->files;
	}

	/**
	 * 获得某目录的二级目录
	 * @param function $condition 此参数必须是一个函数的完整形式,
	 *                 如 'strlen("$file")>2',记得要用单引号括起整个表达式,用双引号括起 $file 字样
	 */
	public function getSubDirs($condition='strlen("$file")>0')
	{
		$subdirs = array();
		if(!function_exists(substr($condition, 0, strpos($condition, "(")))){
			trigger_error("\$condition='$condition' 中所指定的函数名不存在");
		}

		$dh = opendir($this->dirname);
		while(($file = readdir($dh)) !== false){
			if($file != "." && $file != ".."
			&& is_dir($this->dirname."/".$file)
			&& eval("return $condition;")){
				$subdirs[] = $file;
			}
		}
		closedir($dh);
		return $subdirs;
	}

	public function error($msg)
	{
		$this->msg = "<font color='red'>$msg</font>";
		trigger_error($this->msg, E_USER_NOTICE);
	}
}

//test
/*
if(str_replace("\\", "/", $_SERVER["SCRIPT_FILENAME"]) == str_replace("\\", "/", __FILE__)){
header("content-type:text/html;charset=utf-8");
echo ("Test Class LIN_Dir: <br />");
$dir = new LIN_Dir("1");
*/
//创建多层目录:
/**
if($dir->make("123/34d"))
	echo "成功";
else
	echo "失败";
*/
//$dir->make();
//$dir2 = new LIN_Dir("./asd");
//$dir->copy("1", $dir2);
//$dir->copy(".", "2");
#$dir->cd("."); //把本目录对象的工作空间跳转到 . 目录下
#$dir->copyTo("1"); //复制当前目录到 目录 ./1 下面

/*
//创建十个文件, 在每个文件里写入 "hello"
for($i=0; $i<10; $i++){
	$dir->createFile($i.".txt", "hello");
}
*/
//对当前 $dir->dirname 的文件目录的文件写入 "test"
#$arr = $dir->travel('file_put_contents($file, "test");');
?>